#include <iostream>
#include <cstdlib>
#include <fstream>
#include <sstream>
#include <cstring>
#include "comopencl.h"

using namespace std;

ComOpenCL::ComOpenCL(int type, string kernelfile, string kernelname){
	// 0: GPU 1:CPU 2:ALL
	size_t deviceListSize;
	cl_uint numPlatforms;
	status = clGetPlatformIDs(0, NULL, &numPlatforms);
	if (status != CL_SUCCESS)
	{
		printf("Error : Getting Platforms.\
			   			   			   (clGetPlatformsIDs)\n");
		exit(EXIT_FAILURE);
	}

	if (numPlatforms > 0)
	{
		cout << "Platform number : " << numPlatforms << endl;
		cl_platform_id * platforms =
			(cl_platform_id *)malloc(numPlatforms*sizeof(cl_platform_id));
		status = clGetPlatformIDs(numPlatforms, platforms, NULL);

		if (status != CL_SUCCESS)
		{
			printf("Error : Getting Platform IDs.\
				   				   				(clGetPlatformsIDs)\n");
			exit(EXIT_FAILURE);
		}

		for (unsigned int i = 0; i < numPlatforms; i++)
		{
			char pbuff[100];
			status = clGetPlatformInfo(
				platforms[i],
				CL_PLATFORM_VENDOR,
				sizeof(pbuff),
				pbuff,
				NULL);

			platform = platforms[i];
			printf("Platform vendor:%s\n", pbuff);
			if (!strcmp(pbuff, "Advanced Micro Devices, Inc."))
			{
				break;
			}
		}
		delete platforms;
	}

	cl_context_properties cps[3] = {
		CL_CONTEXT_PLATFORM,
		(cl_context_properties)platform, 0 };

	cl_context_properties* cprops = (NULL == platform) ? NULL : cps;

	switch (type){
	case 0:
		context = clCreateContextFromType(
			cprops,
			CL_DEVICE_TYPE_GPU,
			NULL,
			NULL,
			&status);

		if (status != CL_SUCCESS)
		{
			printf("Error : Creating Context.\
				   			   			   			   (clCreateContextFromType)\n");
			exit(EXIT_FAILURE);
		}
		break;
	case 1:
		context = clCreateContextFromType(
			cprops,
			CL_DEVICE_TYPE_CPU,
			NULL,
			NULL,
			&status);

		if (status != CL_SUCCESS)
		{
			printf("Error : Creating Context.\
				   			   			   			   (clCreateContextFromType)\n");
			exit(EXIT_FAILURE);
		}
		break;
	case 2:
		context = clCreateContextFromType(
			cprops,
			CL_DEVICE_TYPE_ALL,
			NULL,
			NULL,
			&status);

		if (status != CL_SUCCESS)
		{
			printf("Error : Creating Context.\
				   			   			   			   (clCreateContextFromType)\n");
			exit(EXIT_FAILURE);
		}
		break;
	default:
		cout << "device type error !" << endl;
		exit(EXIT_FAILURE);
	}

	

	status = clGetContextInfo(context,
		CL_CONTEXT_DEVICES,
		0,
		NULL,
		&deviceListSize);
	if (status != CL_SUCCESS)
	{
		printf("Error: Getting Context Info.\
			   			   				(device list size :clGetContextInfo)\n");
		exit(EXIT_FAILURE);
	}
	cout << "Device number : " << deviceListSize / sizeof(cl_device_id) << endl;
	devices = (cl_device_id *)malloc(deviceListSize);
	if (devices == 0)
	{
		printf("Error: No device found.\n");
		exit(EXIT_FAILURE);
	}

	status = clGetContextInfo(context,
		CL_CONTEXT_DEVICES,
		deviceListSize,
		devices,
		NULL);
	if (status != CL_SUCCESS)
	{
		printf("Error: Getting Context Info.\
			   			   			   (device list :clGetContextInfo)\n");
		exit(EXIT_FAILURE);
	}
	char devinfo[100];
	status = clGetDeviceInfo(devices[0], CL_DEVICE_NAME, 100, devinfo, NULL);
	if (status != CL_SUCCESS){
		cout << "Get device name error !" << endl;
		exit(EXIT_FAILURE);
	}
	cout << "Device name :" << devinfo << endl;


	status = clGetDeviceInfo(devices[0], CL_DEVICE_VENDOR, 100, devinfo, NULL);
	if (status != CL_SUCCESS){
		cout << "Get developer name error !" << endl;
		exit(EXIT_FAILURE);
	}
	cout << "Device developer name :" << devinfo << endl;

	cl_device_type devtype;
	status = clGetDeviceInfo(devices[0], CL_DEVICE_TYPE, sizeof(cl_device_type), &devtype, NULL);
	if (status == CL_SUCCESS){
		cout << "Device type :" << devtype << endl;
	}
	else{
		cout << "Get device type error !" << endl;
		exit(EXIT_FAILURE);
	}
	cl_uint computnum;
	status = clGetDeviceInfo(devices[0], CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(cl_uint), &computnum, NULL);
	if (status == CL_SUCCESS){
		cout << "Max compute units :" << computnum << endl;
	}
	else{
		cout << "Get max compute units error !" << endl;
		exit(EXIT_FAILURE);
	}

	cl_uint clockfreq;
	status = clGetDeviceInfo(devices[0], CL_DEVICE_MAX_CLOCK_FREQUENCY, sizeof(cl_uint), &clockfreq, NULL);
	if (status == CL_SUCCESS){
		cout << "Max clock frequency :" << clockfreq << "MHZ" << endl;
	}
	else{
		cout << "Get max clock frequency error !" << endl;
		exit(EXIT_FAILURE);
	}
	commandQueue = clCreateCommandQueue(context,
		devices[0],
		CL_QUEUE_PROFILING_ENABLE,
		&status);
	if (status != CL_SUCCESS)
	{
		printf("Error: Creating Command Queue.\
			   			   			   (clCreateCommandQueue)\n");
		exit(EXIT_FAILURE);
	}
	string kernelSourceCode;
	getSource(kernelfile, kernelSourceCode);
	const char * kernelsource = kernelSourceCode.c_str();
	const size_t kernelsourcesize = kernelSourceCode.size();
	program = clCreateProgramWithSource(context,
		1,
		&kernelsource,
		&kernelsourcesize,
		&status);

	if (status != CL_SUCCESS)
	{
		printf("Error: Loading source code into program.\
			   			   			   (clCreateProgramWithSource)\n");
		exit(EXIT_FAILURE);
	}

	status = clBuildProgram(program,
		1,
		devices,
		NULL,
		NULL,
		NULL);

	if (status != CL_SUCCESS)
	{
		printf("Error: Building Program.\
			   			   			   (clBuildProgram)\n");
		size_t len;
		char buffer[2048];
		disPlayError(status);
		clGetProgramBuildInfo(program, devices[0], CL_PROGRAM_BUILD_LOG, sizeof(buffer), buffer, &len);
		std::cout << "Build Log :"<<buffer << std::endl;
		cl_build_status buildstatus;
		status = clGetProgramBuildInfo(program,
			devices[0],
			sizeof(buildstatus),
			CL_PROGRAM_BUILD_STATUS,
			&buildstatus,
			NULL);
		cout << "Build status:";
		switch (buildstatus){
		case CL_BUILD_NONE:
			cout << "Build none !" << endl;
			break;
		case CL_BUILD_ERROR:
			cout << "Build Error !" << endl;
			break;
		case CL_BUILD_SUCCESS:
			cout << "Build Success !" << endl;
			break;
		case CL_BUILD_IN_PROGRESS:
			cout << "Build In Prograss" << endl;
			break;
		}
		exit(EXIT_FAILURE);
	}
	kernel = clCreateKernel(program,
		kernelname.c_str(),
		&status);
	if (status != CL_SUCCESS)
	{
		printf("Error: Creating kernel from program .\
			   			   			   			   			   (clCreateKernel)\n");
		exit(EXIT_FAILURE);
	}
	

}

void ComOpenCL::setKernelArg(cl_int index, size_t argsize, const void *arg){
	status = clSetKernelArg(kernel, index, argsize, arg);

	if (status != CL_SUCCESS)
	{
		printf("Error: Setting kernel %d argument.\
			   			   			   (clSetKernelArg)\n",index);
		exit(EXIT_FAILURE);
	}
}

void ComOpenCL::execTask(cl_uint dimension,size_t *globalsize, size_t *localsize,cl_event * event){
	
	status = clEnqueueNDRangeKernel(
		commandQueue,
		kernel,
		dimension,
		NULL,
		globalsize,
		localsize,
		0,
		NULL,
		event);
	if (status != CL_SUCCESS)
	{
		printf("Error: Enqueueing Kernel.\
			   			   			   (clEnqueueNDRangeKernel)\n");
		exit(EXIT_FAILURE);
	}

	status = clFinish(commandQueue);
	if (status != CL_SUCCESS)
	{
		printf("Error: Finish command queue.\
			   			   			   (clFinish)\n");
		exit(EXIT_FAILURE);
	}
}


void ComOpenCL::collectResult(cl_mem buffer, size_t size, void * value_ptr){
	status = clEnqueueReadBuffer(commandQueue, buffer, CL_TRUE, 0, size, value_ptr, 0, NULL, NULL);
	if (status != CL_SUCCESS)
	{
		printf("collect result error !\n");
		exit(EXIT_FAILURE);
	}
	clFinish(commandQueue);
	if (status != CL_SUCCESS)
	{
		printf("Error: Finish collect result command queue.\
			   			   			   			   (clFinish)\n");
		exit(EXIT_FAILURE);
	}
}

void ComOpenCL::getSource(string kernelfile, string &source){
	ifstream kernelFile(kernelfile.c_str(), ios::in);
	if (!kernelFile.is_open()){
		cout << "open file " << kernelfile << " error!" << endl;
		exit(1);
	}

	ostringstream oss;
	oss << kernelFile.rdbuf();
	source = oss.str();
	kernelFile.close();
}

ComOpenCL::~ComOpenCL(){
	status = clReleaseKernel(kernel);
	status = clReleaseProgram(program);
	status = clReleaseCommandQueue(commandQueue);
	status = clReleaseContext(context);
	free(devices);
}

void ComOpenCL::disPlayError(cl_int error_code){
	switch (error_code){

		//Enqueue Kernel error code :
	case CL_INVALID_PROGRAM_EXECUTABLE:
		cout << "CL_INVALID_PROGRAM_EXECUTABLE" << endl;
		break;
	case CL_INVALID_COMMAND_QUEUE:
		cout << "CL_INVALID_COMMAND_QUEUE" << endl;
		break;
	case CL_INVALID_KERNEL:
		cout << "CL_INVALID_KERNEL" << endl;
		break;
	case CL_INVALID_CONTEXT:
		cout << "CL_INVALID_CONTEXT" << endl;
		break;
	case CL_INVALID_KERNEL_ARGS:
		cout << "CL_INVALID_KERNEL_ARGS " << endl;
		break;
	case CL_INVALID_WORK_DIMENSION:
		cout << "CL_INVALID_WORK_DIMENSION" << endl;
		break;
	case CL_INVALID_GLOBAL_WORK_SIZE:
		cout << "CL_INVALID_GLOBAL_WORK_SIZE" << endl;
		break;
	case CL_INVALID_GLOBAL_OFFSET:
		cout << "CL_INVALID_GLOBAL_OFFSET" << endl;
		break;
	case CL_INVALID_WORK_GROUP_SIZE:
		cout << "CL_INVALID_WORK_GROUP_SIZE" << endl;
		break;
	case CL_INVALID_WORK_ITEM_SIZE:
		cout << "CL_INVALID_WORK_ITEM_SIZE" << endl;
		break;
	case CL_MISALIGNED_SUB_BUFFER_OFFSET:
		cout << "CL_MISALIGNED_SUB_BUFFER_OFFSET" << endl;
		break;
	case CL_INVALID_IMAGE_SIZE:
		cout << "CL_INVALID_IMAGE_SIZE" << endl;
		break;
	case CL_MEM_OBJECT_ALLOCATION_FAILURE:
		cout << "CL_MEM_OBJECT_ALLOCATION_FAILURE" << endl;
		break;
	case CL_INVALID_EVENT_WAIT_LIST:
		cout << "CL_INVALID_EVENT_WAIT_LIST" << endl;
		break;
	case CL_OUT_OF_RESOURCES:
		cout << "CL_OUT_OF_RESOURCES" << endl;
		break;
	case CL_OUT_OF_HOST_MEMORY:
		cout << "CL_OUT_OF_HOST_MEMORY" << endl;
		break;

		//build program error code:
	case CL_INVALID_PROGRAM:
		cout << "CL_INVALID_PROGRAM" << endl;
		break;
	case CL_INVALID_VALUE:
		cout << "CL_INVALID_VALUE" << endl;
		break;
	case CL_INVALID_DEVICE:
		cout << "CL_INVALID_DEVICE " << endl;
		break;
	case CL_INVALID_BINARY:
		cout << "CL_INVALID_BINARY" << endl;
		break;
	case CL_INVALID_BUILD_OPTIONS:
		cout << "CL_INVALID_BUILD_OPTIONS" << endl;
		break;
	case CL_INVALID_OPERATION:
		cout << "CL_INVALID_OPERATION " << endl;
		break;
	case CL_COMPILER_NOT_AVAILABLE:
		cout << "CL_COMPILER_NOT_AVAILABLE" << endl;
		break;
	case CL_BUILD_PROGRAM_FAILURE:
		cout << "CL_BUILD_PROGRAM_FAILURE" << endl;
		break;

	default:
		cout << "Unknown " << endl;
		break;
	}
}
